/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2007-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.grizzly;
import com.sun.grizzly.Controller.Protocol;
import com.sun.grizzly.async.AsyncQueueDataProcessor;
import com.sun.grizzly.async.AsyncQueueReadUnit;
import com.sun.grizzly.async.AsyncQueueWriteUnit;
import com.sun.grizzly.async.AsyncReadCallbackHandler;
import com.sun.grizzly.async.AsyncReadCondition;
import com.sun.grizzly.async.AsyncWriteCallbackHandler;
import com.sun.grizzly.async.ByteBufferCloner;
import com.sun.grizzly.util.OutputWriter;
import com.sun.grizzly.util.SSLOutputWriter;
import com.sun.grizzly.util.SSLUtils;
import java.io.EOFException;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
/**
* <p>
* Non blocking SSL Connector Handler. The recommended way to use this class
* is by creating an external Controller and share the same SelectorHandler
* instance.
* </p><p>
* Recommended
* -----------
* </p><p><pre><code>
* Controller controller = new Controller();
* // new SSLSelectorHandler(true) means the Selector will be used only
* // for client operation (OP_READ, OP_WRITE, OP_CONNECT).
* SSLSelectorHandler sslSelectorHandler = new SSLSelectorHandler(true);
* controller.setSelectorHandler(sslSelectorHandler);
* SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
* sslConnectorHandler.connect(localhost,port, new SSLCallbackHandler(){...},
* sslSelectorHandler);
* SSLConnectorHandler sslConnectorHandler2 = new SSLConnectorHandler();
* sslConnectorHandler2.connect(localhost,port, new SSLCallbackHandler(){...},
* sslSelectorHandler);
* </code></pre></p><p>
* Not recommended (but still works)
* ---------------------------------
* </p><p><pre><code>
* SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
* sslConnectorHandler.connect(localhost,port);
*
* Internally, an new Controller will be created everytime connect(localhost,port)
* is invoked, which has an impact on performance.
*
* As common comment: developer should be very careful if dealing directly with
* <code>SSLConnectorHandler</code>'s underlying socket channel! In most cases
* there is no need to do this, but use read, write methods provided
* by <code>SSLConnectorHandler</code>
* </code></pre></p>
*
* @author Alexey Stashok
* @author Jeanfrancois Arcand
*/
public class SSLConnectorHandler
extends AbstractConnectorHandler<SSLSelectorHandler, SSLCallbackHandler> {
/**
* Default Logger.
*/
private static Logger logger = Logger.getLogger("grizzly");
/*
* An empty ByteBuffer used for handshaking
*/
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
/**
* Input buffer for reading encrypted data from channel
*/
private ByteBuffer securedInputBuffer;
/**
* Output buffer, which contains encrypted data ready for writing to channel
*/
private ByteBuffer securedOutputBuffer;
/**
* Buffer, where application data could be written during a asynchronous handshaking.
* It is set when user application calls: SSLConnectorHandler.handshake(appDataBuffer)
* and references appDataBuffer.
*/
private ByteBuffer asyncHandshakeBuffer;
/**
* Is the handshake phase completed
*/
private volatile boolean isHandshakeDone;
/**
* IsConnected Latch related
*/
private volatile CountDownLatch isConnectedLatch;
/**
* Are we creating a controller every run.
*/
private boolean isStandalone = false;
/**
* Is async handshake in progress
*/
private boolean isProcessingAsyncHandshake;
/**
* Result of last{@link SSLEngine} operation
*/
private SSLEngineResult sslLastOperationResult;
/**
* Current handshake status
*/
private SSLEngineResult.HandshakeStatus handshakeStatus;
/**
* Current{@link SSLEngine} status
*/
private SSLEngineResult.Status sslEngineStatus = null;
/**
* Are we creating a controller every run.
*/
private boolean delegateSSLTasks;
/**
* Connector's{@link SSLEngine}
*/
private SSLEngine sslEngine;
/**
* Connector's {@link SSLContext}
*/
private SSLContext sslContext;
/**
* SSL read postprocessor for <code>AsyncQueueReadable</code>
*/
private final AsyncQueueDataProcessor sslReadPostProcessor;
/**
* SSL write preprocessor for <code>AsyncQueueWritable</code>
*/
private final AsyncQueueDataProcessor sslWritePreProcessor;
/**
* Connector's write mode
*/
private boolean isAsyncWriteQueueMode;
/**
* Connector's read mode
*/
private boolean isAsyncReadQueueMode;
public SSLConnectorHandler() {
this(SSLConfig.DEFAULT_CONFIG.createSSLContext());
}
public SSLConnectorHandler(SSLConfig sslConfig) {
this(sslConfig.createSSLContext());
}
public SSLConnectorHandler(SSLContext sslContext) {
this.sslContext = sslContext;
sslReadPostProcessor = new SSLReadPostProcessor();
sslWritePreProcessor = new SSLWritePreProcessor();
protocol(Protocol.TLS);
}
public boolean getDelegateSSLTasks() {
return delegateSSLTasks;
}
public void setDelegateSSLTasks(boolean delegateSSLTasks) {
this.delegateSSLTasks = delegateSSLTasks;
}
/**
* Connect to hostname:port. When an aysnchronous event happens (e.g
* OP_READ or OP_WRITE), the {@link Controller} will invoke
* the {@link CallbackHandler}.
* @param remoteAddress remote address to connect
* @param localAddress local address to bin
* @param callbackHandler the handler invoked by its associated {@link SelectorHandler} when
* a non blocking operation is ready to be handled. When null, all
* read and write operation will be delegated to the default
* {@link ProtocolChain} and its list of {@link ProtocolFilter}
* . When null, this {@link ConnectorHandler} will create an instance of {@link DefaultCallbackHandler}.
* @param selectorHandler an instance of SelectorHandler.
* @throws java.io.IOException
*/
public void connect(SocketAddress remoteAddress, SocketAddress localAddress,
SSLCallbackHandler callbackHandler,
SSLSelectorHandler selectorHandler) throws IOException {
if (isConnected) {
throw new AlreadyConnectedException();
}
if (controller == null) {
throw new IllegalStateException("Controller cannot be null");
}
if (selectorHandler == null) {
throw new IllegalStateException("SelectorHandler cannot be null");
}
this.selectorHandler = selectorHandler;
if (callbackHandler == null){
callbackHandler = new DefaultCallbackHandler(this);
} else {
this.callbackHandler = callbackHandler;
}
// Wait for the onConnect to be invoked.
isConnectedLatch = new CountDownLatch(1);
selectorHandler.connect(remoteAddress, localAddress,
new SSLInternalCallbackHandler());
try {
isConnectedLatch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
throw new IOException(ex.getMessage());
}
}
/**
* Connect to hostname:port. Internally an instance of Controller and
* its default SelectorHandler will be created everytime this method is
* called. This method should be used only and only if no external
* Controller has been initialized.
* @param remoteAddress remote address to connect
* @throws java.io.IOException
* @param localAddress local address to bin
*/
public void connect(SocketAddress remoteAddress, SocketAddress localAddress)
throws IOException {
if (isConnected) {
throw new AlreadyConnectedException();
}
if (controller == null) {
isStandalone = true;
controller = new Controller();
controller.setSelectorHandler(new SSLSelectorHandler(true));
final CountDownLatch latch = new CountDownLatch(1);
controller.addStateListener(new ControllerStateListenerAdapter() {
@Override
public void onReady() {
latch.countDown();
}
@Override
public void onException(Throwable e) {
if (latch.getCount() > 0) {
logger.log(Level.SEVERE, "Error occured on Controller startup: ", e);
}
latch.countDown();
}
});
callbackHandler = new DefaultCallbackHandler(this,false);
controller.executeUsingKernelExecutor();
try {
latch.await();
} catch (InterruptedException ex) {
}
}
connect(remoteAddress, localAddress, callbackHandler);
}
/**
* Initiate SSL handshake phase.
* Handshake is required to be done after connection established.
*
* @param byteBuffer Application {@link ByteBuffer}, where application data
* will be stored
* @param blocking true, if handshake should be done in blocking mode, for non-blocking false
* @return If blocking parameter is true - method should always return true if handshake is done,
* or throw IOException otherwise. For non-blocking mode method returns true if handshake is done, or false
* if handshake will be completed in non-blocking manner.
* If False returned - <code>SSLConnectorHandler</code> will call callbackHandler.onHandshake() to notify
* about finishing handshake phase.
* @throws java.io.IOException if some error occurs during processing I/O operations/
*/
public boolean handshake(ByteBuffer byteBuffer, boolean blocking)
throws IOException {
sslEngine.beginHandshake();
handshakeStatus = sslEngine.getHandshakeStatus();
if (blocking) {
SSLUtils.doHandshake(underlyingChannel, byteBuffer, securedInputBuffer,
securedOutputBuffer, sslEngine, handshakeStatus);
securedOutputBuffer.limit(securedOutputBuffer.position());
finishHandshake();
// Sync should be always done
return true;
} else {
return doAsyncHandshake(byteBuffer);
}
}
/**
* Read bytes. If blocking is set to <tt>true</tt>, a pool of temporary
* {@link Selector} will be used to read bytes.
* @param byteBuffer The byteBuffer to store bytes.
* @param blocking <tt>true</tt> if a a pool of temporary Selector
* is required to handle a blocking read.
* @return number of bytes read from a channel.
* Be careful, because return value represents the length of encrypted data,
* which was read from a channel. Don't use return value to determine the
* availability of a decrypted data to process, but use byteBuffer.remaining().
* @throws java.io.IOException
*/
@Override
public long read(ByteBuffer byteBuffer, boolean blocking)
throws IOException {
if (!isConnected) {
throw new NotYetConnectedException();
}
if (blocking) {
return SSLUtils.doSecureRead(underlyingChannel, sslEngine,
byteBuffer, securedInputBuffer).bytesRead;
} else {
isAsyncReadQueueMode = false;
int nRead = doReadAsync(byteBuffer);
if (nRead == 0) {
registerSelectionKeyFor(SelectionKey.OP_READ);
}
return nRead;
}
}
/**
* Writes bytes. If blocking is set to <tt>true</tt>, a pool of temporary
* {@link Selector} will be used to writes bytes.
* @param byteBuffer The byteBuffer to write.
* @param blocking <tt>true</tt> if a a pool of temporary Selector
* is required to handle a blocking write.
* @return number of bytes written on a channel.
* Be careful, as non-crypted data is passed, but crypted data is written
* on channel. Don't use return value to determine the
* number of bytes from original buffer, which were written.
* @throws java.io.IOException
*/
@Override
public long write(ByteBuffer byteBuffer, boolean blocking)
throws IOException {
if (!isConnected) {
throw new NotYetConnectedException();
}
if (blocking) {
long nWrite = SSLOutputWriter.flushChannel(underlyingChannel,
byteBuffer, securedOutputBuffer, sslEngine);
// Mark securedOutputBuffer as empty
securedOutputBuffer.position(securedOutputBuffer.limit());
return nWrite;
} else {
if (callbackHandler == null) {
throw new IllegalStateException("Non blocking write needs a CallbackHandler");
}
isAsyncWriteQueueMode = false;
int nWrite = 1;
int totalWrite = 0;
while (nWrite > 0 &&
(byteBuffer.hasRemaining() ||
securedOutputBuffer.hasRemaining())) {
nWrite = doWriteAsync(byteBuffer);
totalWrite += nWrite;
}
if (byteBuffer.hasRemaining() ||
securedOutputBuffer.hasRemaining()) {
registerSelectionKeyFor(SelectionKey.OP_WRITE);
}
return totalWrite;
}
}
/**
* {@inheritDoc}
*/
@Override
public Future<AsyncQueueReadUnit> readFromAsyncQueue(ByteBuffer buffer,
AsyncReadCallbackHandler callbackHandler,
AsyncReadCondition condition,
AsyncQueueDataProcessor readPostProcessor) throws IOException {
isAsyncReadQueueMode = true;
return super.readFromAsyncQueue( buffer,
callbackHandler,
condition,
readPostProcessor != null ? readPostProcessor : obtainSSLReadPostProcessor() );
}
/**
* {@inheritDoc}
*/
@Override
public Future<AsyncQueueWriteUnit> writeToAsyncQueue(ByteBuffer buffer,
AsyncWriteCallbackHandler callbackHandler,
AsyncQueueDataProcessor writePreProcessor,
ByteBufferCloner cloner) throws IOException {
isAsyncWriteQueueMode = true;
return super.writeToAsyncQueue( buffer,
callbackHandler,
writePreProcessor != null ? writePreProcessor : obtainSSLWritePreProcessor(),
cloner );
}
/**
* {@inheritDoc}
*/
@Override
public Future<AsyncQueueWriteUnit> writeToAsyncQueue(
SocketAddress dstAddress, ByteBuffer buffer,
AsyncWriteCallbackHandler callbackHandler,
AsyncQueueDataProcessor writePreProcessor, ByteBufferCloner cloner)
throws IOException {
isAsyncWriteQueueMode = true;
return super.writeToAsyncQueue( dstAddress,
buffer,
callbackHandler,
writePreProcessor != null ? writePreProcessor : obtainSSLWritePreProcessor(),
cloner );
}
/**
* Close the underlying connection.
*/
public void close() throws IOException {
if (logger.isLoggable(Level.FINE)) {
IOException ioe = new IOException("Logging stacktrace...");
logger.log(Level.FINE, "Closing SSLConnectorHandler " + this +
" Channel: " + underlyingChannel + " engine: " + sslEngine, ioe);
}
if (underlyingChannel != null) {
if (isConnected) {
try {
if (securedOutputBuffer.hasRemaining()) {
// if there is something is securedOutputBuffer - flush it
OutputWriter.flushChannel(underlyingChannel,
securedOutputBuffer);
}
// Close secure outbound channel and flush data
sslEngine.closeOutbound();
SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine);
OutputWriter.flushChannel(underlyingChannel,
securedOutputBuffer);
} catch (IOException e) {
logger.log(Level.FINE,
"IOException during closing the connector.", e);
}
}
if (selectorHandler != null) {
SelectionKey key =
selectorHandler.keyFor(underlyingChannel);
if (key == null) {
return;
}
selectorHandler.getSelectionKeyHandler().close(key);
}
underlyingChannel.close();
}
if (controller != null && isStandalone) {
controller.stop();
controller = null;
}
sslEngine = null;
asyncHandshakeBuffer = null;
isStandalone = false;
isConnected = false;
isHandshakeDone = false;
}
/**
* Finish handling the OP_CONNECT interest ops.
* @param key - a {@link SelectionKey}
*/
public void finishConnect(SelectionKey key) throws IOException{
try {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Finish connect");
}
final SocketChannel socketChannel = (SocketChannel) key.channel();
underlyingChannel = socketChannel;
socketChannel.finishConnect();
isConnected = socketChannel.isConnected();
if (isConnected) {
initSSLEngineIfRequired();
}
} catch (IOException e) {
throw e;
} finally {
isConnectedLatch.countDown();
}
}
/**
* Changes SSLConnectorHandler state, after handshake operation is done.
* Normally should not be called by outside Grizzly, just in case when custom
* handshake code was used.
*/
public void finishHandshake() {
isProcessingAsyncHandshake = false;
isHandshakeDone = true;
}
/**
* A token decribing the protocol supported by an implementation of this
* interface
* @return this {@link ConnectorHandler}'s protocol
*/
@Override
public Controller.Protocol protocol() {
return Controller.Protocol.TLS;
}
/**
* Is the underlying SocketChannel connected.
* @return <tt>true</tt> if connected, otherwise <tt>false</tt>
*/
public boolean isHandshakeDone() {
return isHandshakeDone && !isProcessingAsyncHandshake;
}
/**
* Get SSLConnector's {@link SSLContext}
*/
public SSLContext getSSLContext() {
return sslContext;
}
/**
* Set {@link SSLContext}.
* Use this method to change SSLConnectorHandler configuration.
* New configuration will become active only after SSLConnector
* will be closed and connected again.
*/
public void setSSLContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
/**
* Configure SSLConnectorHandler's SSL settings.
*
* Use this method to change SSLConnectorHandler configuration.
* New configuration will become active only after SSLConnector
* will be closed and connected again.
*/
public void configure(SSLConfig sslConfig) {
this.sslContext = sslConfig.createSSLContext();
}
/**
* Returns SSLConnector's{@link SSLEngine}
* @return{@link SSLEngine}
*/
public SSLEngine getSSLEngine() {
return sslEngine;
}
/**
* Set{@link SSLEngine}
* @param sslEngine{@link SSLEngine}
*/
public void setSSLEngine(SSLEngine sslEngine) {
this.sslEngine = sslEngine;
}
/**
* Returns <code>SSLConnectorHandler</code>'s secured input buffer, it
* uses for reading data from a socket channel.
* @return secured input {@link ByteBuffer}
*/
public ByteBuffer getSecuredInputBuffer() {
return securedInputBuffer;
}
/**
* Returns <code>SSLConnectorHandler</code>'s secured output buffer, it
* uses for writing data to a socket channel.
* @return secured output {@link ByteBuffer}
*/
public ByteBuffer getSecuredOutputBuffer() {
return securedOutputBuffer;
}
/**
* Gets the size of the largest application buffer that may occur when
* using this session.
* SSLEngine application data buffers must be large enough to hold the
* application data from any inbound network application data packet
* received. Typically, outbound application data buffers can be of any size.
*
* (javadoc is taken from SSLSession.getApplicationBufferSize())
* @return largets application buffer size, which may occur
*/
public int getApplicationBufferSize() {
initSSLEngineIfRequired();
return sslEngine.getSession().getApplicationBufferSize();
}
/**
* Read a data from channel in async mode and decrypt
* @param byteBuffer buffer for decrypted data
* @return number of bytes read from a channel
* @throws java.io.IOException
*/
private int doReadAsync(ByteBuffer byteBuffer) throws IOException {
// Clear or compact secured input buffer
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler compactBuffer" +
" securedInputBuffer: " + securedInputBuffer);
}
clearOrCompactBuffer(securedInputBuffer);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler read" +
" securedInputBuffer: " + securedInputBuffer);
}
// Read data to secured buffer
int bytesRead = -1;
try{
bytesRead = ((SocketChannel) underlyingChannel).read(securedInputBuffer);
} finally {
if (bytesRead == -1){
SelectionKeyHandler skh = selectorHandler.getSelectionKeyHandler();
if (skh instanceof BaseSelectionKeyHandler){
((DefaultSelectionKeyHandler)skh).notifyRemotlyClose(getSelectionKey());
}
}
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler done read" +
" securedInputBuffer: " + securedInputBuffer +
" bytesRead: " + bytesRead);
}
if (bytesRead == -1) {
try {
sslEngine.closeInbound();
// check if there is some secured data still available
if (securedInputBuffer.position() == 0 ||
sslEngineStatus == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
return -1;
}
} catch (SSLException e) {
return -1;
}
}
securedInputBuffer.flip();
if (bytesRead == 0 && !securedInputBuffer.hasRemaining()) {
return 0;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler unwrapall" +
" securedInputBuffer: " + securedInputBuffer);
}
int bytesProduced = unwrapAll(byteBuffer);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler unwrapall done" +
" securedInputBuffer: " + securedInputBuffer,
" produced: " + bytesProduced +
" status: " + sslEngineStatus);
}
if (bytesProduced == 0) {
if (sslEngineStatus == SSLEngineResult.Status.CLOSED) {
return -1;
} else if (sslEngineStatus == SSLEngineResult.Status.BUFFER_OVERFLOW) {
throw new BufferOverflowException();
}
}
return bytesRead;
}
private int unwrapAll(ByteBuffer byteBuffer) throws SSLException {
SSLEngineResult result = null;
int bytesProduced = 0;
do {
result = sslEngine.unwrap(securedInputBuffer, byteBuffer);
bytesProduced += result.bytesProduced();
// During handshake phase several unwrap actions could be executed on read data
} while (result.getStatus() == SSLEngineResult.Status.OK &&
(isHandshakeDone || (result.getHandshakeStatus() ==
SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
result.bytesProduced() == 0)));
updateSSLEngineStatus(result);
return bytesProduced;
}
/**
* Write secured data to channel in async mode
*
* @param byteBuffer non-crypted data buffer
* @return number of bytes written on a channel.
* Be careful, as non-crypted data is passed, but crypted data is written
* on channel. Don't use return value to determine,
* number of bytes from original buffer, which were written.
* @throws java.io.IOException
*/
private int doWriteAsync(ByteBuffer byteBuffer) throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "asyncWrite. securedBuffer: " +
securedOutputBuffer);
}
if (securedOutputBuffer.hasRemaining() && !flushSecuredOutputBuffer()) {
return 0;
}
securedOutputBuffer.clear();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "asyncWrite. wrap sslEngine: " + sslEngine +
" securedOutputBuffer: " + securedOutputBuffer +
" byteBuffer: " + byteBuffer);
}
SSLEngineResult result = SSLUtils.wrap(byteBuffer, securedOutputBuffer, sslEngine);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "asyncWrite. wrap done sslEngine: " + sslEngine +
" securedOutputBuffer: " + securedOutputBuffer +
" byteBuffer: " + byteBuffer +
" status: " + result);
}
updateSSLEngineStatus(result);
int count = ((SocketChannel) underlyingChannel).write(securedOutputBuffer);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "asyncWrite. written: " + count +
" securedOutputBuffer: " + securedOutputBuffer);
}
return count;
}
/**
* Perform an SSL handshake in async mode.
* @param byteBuffer The application {@link ByteBuffer}
*
* @throws IOException if the handshake fail.
*/
private boolean doAsyncHandshake(ByteBuffer byteBuffer) throws IOException {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler.doAsyncHandshake");
}
SSLEngineResult result;
isProcessingAsyncHandshake = true;
asyncHandshakeBuffer = byteBuffer;
while (handshakeStatus != HandshakeStatus.FINISHED) {
switch (handshakeStatus) {
case NEED_WRAP:
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_WRAP sslEngine: " + sslEngine +
" securedBuffer: " + securedOutputBuffer);
}
result = SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_WRAP done sslEngine: " + sslEngine +
" securedBuffer: " + securedOutputBuffer +
" result: " + result +
" handshakeStatus: " + result.getHandshakeStatus());
}
updateSSLEngineStatus(result);
switch (result.getStatus()) {
case OK:
if (!flushSecuredOutputBuffer()) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"SSLConnectorHandler pending secured buffer flush: " + sslEngine +
" securedBuffer: " + securedOutputBuffer);
}
return false;
}
break;
default:
throw new IOException("Handshaking error: " + result.getStatus());
}
if (handshakeStatus != HandshakeStatus.NEED_UNWRAP) {
break;
}
case NEED_UNWRAP:
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP sslEngine: " + sslEngine +
" byteBuffer: " + byteBuffer);
}
int bytesRead = doReadAsync(byteBuffer);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP done sslEngine: " + sslEngine +
" byteBuffer: " + byteBuffer +
" bytesRead: " + bytesRead +
" handshakeStatus: " + handshakeStatus);
}
if (bytesRead == -1) {
try {
sslEngine.closeInbound();
} catch (IOException e) {
logger.log(Level.FINE, "Exception occured when closing sslEngine inbound.", e);
}
throw new EOFException("Connection closed");
} else if (bytesRead == 0 && sslLastOperationResult.bytesConsumed() == 0) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP reregister key sslEngine: " + sslEngine);
}
registerSelectionKeyFor(SelectionKey.OP_READ);
return false;
}
if (handshakeStatus != HandshakeStatus.NEED_TASK) {
break;
}
case NEED_TASK:
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_TASK sslEngine: " + sslEngine);
}
handshakeStatus = executeDelegatedTask();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler NEED_TASK sslEngine: " + sslEngine +
" handshakeStatus: " + handshakeStatus);
}
break;
default:
throw new RuntimeException("Invalid Handshaking State" + handshakeStatus);
}
}
if (isProcessingAsyncHandshake) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "SSLConnectorHandler finishHandshake sslEngine: " + sslEngine);
}
finishHandshake();
}
asyncHandshakeBuffer = null;
return true;
}
/**
* Complete hanshakes operations.
* @return SSLEngineResult.HandshakeStatus
*/
private SSLEngineResult.HandshakeStatus executeDelegatedTask() {
Runnable runnable;
while ((runnable = sslEngine.getDelegatedTask()) != null) {
runnable.run();
}
return sslEngine.getHandshakeStatus();
}
/**
* Update <code>SSLConnectorHandler</code> internal status with
* last{@link SSLEngine} operation result.
*
* @param result last{@link SSLEngine} operation result
*/
private void updateSSLEngineStatus(SSLEngineResult result) {
sslLastOperationResult = result;
sslEngineStatus = result.getStatus();
handshakeStatus = result.getHandshakeStatus();
}
/**
* Clears buffer if there is no info available, or compact buffer otherwise.
* @param buffer byte byffer
*/
private static void clearOrCompactBuffer(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
buffer.clear();
} else if (buffer.remaining() < buffer.capacity()) {
buffer.compact();
}
}
/**
* Gets <code>SSLConnectorHandler</code> {@link SelectionKey}
* @return {@link SelectionKey}
*/
private SelectionKey getSelectionKey() {
return selectorHandler.keyFor(underlyingChannel);
}
/**
* Registers <code>SSLConnectorHandler<code>'s {@link SelectionKey}
* to listen channel operations.
* @param ops interested channel operations
*/
private void registerSelectionKeyFor(int ops) {
SelectionKey key = getSelectionKey();
selectorHandler.register(key, ops);
}
/**
* Flushes as much as possible bytes from the secured output buffer
* @return true if secured buffer was completely flushed, false otherwise
*/
private boolean flushSecuredOutputBuffer() throws IOException {
int nWrite = 1;
try{
while (nWrite > 0 && securedOutputBuffer.hasRemaining()) {
nWrite = ((SocketChannel) underlyingChannel).write(securedOutputBuffer);
}
} catch (IOException ex){
nWrite = -1;
throw ex;
} finally{
if (nWrite == -1){
SelectionKeyHandler skh = selectorHandler.getSelectionKeyHandler();
if (skh instanceof BaseSelectionKeyHandler){
((DefaultSelectionKeyHandler)skh).notifyRemotlyClose(getSelectionKey());
}
}
}
if (securedOutputBuffer.hasRemaining()) {
SelectionKey key = selectorHandler.keyFor(underlyingChannel);
selectorHandler.register(key, SelectionKey.OP_WRITE);
return false;
}
return true;
}
/**
* Initiate{@link SSLEngine} and related secure buffers
*/
private void initSSLEngineIfRequired() {
if (sslEngine == null) {
sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(true);
}
int bbSize = sslEngine.getSession().getPacketBufferSize();
securedInputBuffer = ByteBuffer.allocate(bbSize * 2);
securedOutputBuffer = ByteBuffer.allocate(bbSize * 2);
securedOutputBuffer.limit(0);
}
private AsyncQueueDataProcessor obtainSSLReadPostProcessor() {
return sslReadPostProcessor;
}
private AsyncQueueDataProcessor obtainSSLWritePreProcessor() {
return sslWritePreProcessor;
}
/**
* Internal SSL CallbackHandler, which is able to process properly SSL handshake
* phase and translate its calls to the custom SSLCallbackHandler.
*/
private class SSLInternalCallbackHandler implements CallbackHandler {
public void onConnect(IOEvent ioEvent) {
callbackHandler.onConnect(ioEvent);
}
public void onRead(IOEvent ioEvent) {
if (!isAsyncReadQueueMode) {
try {
// if processing handshake - pass the data to handshake related code
if (isProcessingAsyncHandshake) {
if (doAsyncHandshake(asyncHandshakeBuffer)) {
callbackHandler.onHandshake(ioEvent);
}
return;
}
callbackHandler.onRead(ioEvent);
} catch (IOException e) {
logger.log(Level.SEVERE, "Exception occured when reading from SSL channel.", e);
}
}
}
public void onWrite(IOEvent ioEvent) {
if (!isAsyncWriteQueueMode) {
try {
// check if all the secured data was written, if not -
// flush as much as possible.
if (!securedOutputBuffer.hasRemaining() || flushSecuredOutputBuffer()) {
// if no encrypted data left in buffer - continue processing
if (isProcessingAsyncHandshake) {
if (doAsyncHandshake(asyncHandshakeBuffer)) {
callbackHandler.onHandshake(ioEvent);
}
return;
}
callbackHandler.onWrite(ioEvent);
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Exception occured when writing to SSL channel.", e);
}
}
}
}
/**
* SSL <code>AsyncQueueDataProcessor</code> for a
* <code>TCPAsyncQueueReader</code>
*/
private class SSLReadPostProcessor implements AsyncQueueDataProcessor {
public ByteBuffer getInternalByteBuffer() {
return securedInputBuffer;
}
public void process(ByteBuffer byteBuffer) throws SSLException {
securedInputBuffer.flip();
unwrapAll(byteBuffer);
clearOrCompactBuffer(securedInputBuffer);
}
}
/**
* SSL <code>AsyncQueueDataProcessor</code> for a
* <code>TCPAsyncQueueWriter</code>
*/
private class SSLWritePreProcessor implements AsyncQueueDataProcessor {
public ByteBuffer getInternalByteBuffer() {
return securedOutputBuffer;
}
public void process(ByteBuffer byteBuffer) throws SSLException {
if (!byteBuffer.hasRemaining() ||
securedOutputBuffer.hasRemaining()) return;
securedOutputBuffer.clear();
SSLEngineResult result = sslEngine.wrap(byteBuffer, securedOutputBuffer);
updateSSLEngineStatus(result);
securedOutputBuffer.flip();
}
}
}